#include "JSTypeBridge.h"

jobject uth_to_j_val(JNIEnv *env, UTHValue *uthVal)
{
	if(uthVal->isArray())
		return uth_to_j_array(env, (UTHArray*)uthVal);
	else if(uthVal->isNumber())
		return uth_to_j_number(env, (UTHNumber*)uthVal);
	else if(uthVal->isString())
		return uth_to_j_string(env, (UTHString*)uthVal);
	else if(uthVal->isObject())
		return uth_to_j_object(env, (UTHObject*)uthVal);
	else if(uthVal->isUndefined())
		return uth_to_j_undefined(env);
	else
		return uth_to_j_undefined(env);
}

jobject uth_to_j_object(JNIEnv *env, UTHObject *uthObj)
{
	jclass cls = env->FindClass("org/phnq/babel/types/JSObject");
	jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
	jmethodID putmid = env->GetMethodID(cls, "put", "(Ljava/lang/String;Lorg/phnq/babel/types/JSValue;)V");
	jobject obj = env->NewObject(cls, mid);

	vector<string> keys = uthObj->getKeys();
	int len = keys.size();
	for(int i=0; i<len; i++)
	{
		string key = keys[i];
		UTHValue *val = uthObj->get(key);

		jstring jkey = env->NewStringUTF(key.c_str());
		jobject jval = uth_to_j_val(env, val);
		env->CallVoidMethod(obj, putmid, jkey, jval);
	}
	
	return obj;
}

jobject uth_to_j_array(JNIEnv *env, UTHArray *uthArr)
{
	jclass cls = env->FindClass("org/phnq/babel/types/JSArray");
	jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
	jmethodID addmid = env->GetMethodID(cls, "add", "(Lorg/phnq/babel/types/JSValue;)V");
	jobject obj = env->NewObject(cls, mid);

	for(int i=0; i<uthArr->length(); i++)
	{
		UTHValue *val = uthArr->get(i);
		jobject jval = uth_to_j_val(env, val);
		env->CallVoidMethod(obj, addmid, jval);
	}
	
	return obj;
}

jobject uth_to_j_number(JNIEnv *env, UTHNumber *uthNum)
{
	jclass cls = env->FindClass("org/phnq/babel/types/JSNumber");
	if(uthNum->isInt())
	{
		jmethodID mid = env->GetMethodID(cls, "<init>", "(I)V");
		return env->NewObject(cls, mid, (jint)uthNum->intValue());
	}
	else
	{
		jmethodID mid = env->GetMethodID(cls, "<init>", "(D)V");
		return env->NewObject(cls, mid, (jdouble)uthNum->doubleValue());
	}
}

jobject uth_to_j_string(JNIEnv *env, UTHString *uthStr)
{
	jclass cls = env->FindClass("org/phnq/babel/types/JSString");
	jmethodID mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
	return env->NewObject(cls, mid, env->NewStringUTF(uthStr->getChars()));
}

jobject uth_to_j_undefined(JNIEnv *env)
{
	jclass cls = env->FindClass("org/phnq/babel/types/JSUndefined");
	jmethodID mid = env->GetStaticMethodID(cls, "getInstance", "()Lorg/phnq/babel/types/JSUndefined;");
	return env->CallStaticObjectMethod(cls, mid);
}


UTHValue* j_to_uth_val(JNIEnv *env, jobject j)
{
	jclass jCls = env->GetObjectClass(j);
	jmethodID mid = env->GetMethodID(jCls, "getType", "()S");
	jshort type = env->CallShortMethod(j, mid);
	
	switch(type)
	{
		case 0:
			return j_to_uth_string(env, jCls, j);

		case 1:
			return j_to_uth_number(env, jCls, j);

		case 2:
			return UTHUndefined::getInstance();

		case 3:
			return j_to_uth_object(env, jCls, j);

		case 4:
			return j_to_uth_array(env, jCls, j);
		
		default:
			return UTHUndefined::getInstance();
	}
}

UTHValue* j_to_uth_string(JNIEnv *env, jclass jCls, jobject j)
{
	jmethodID mid = env->GetMethodID(jCls, "stringValue", "()Ljava/lang/String;");
	jstring jstringVal = (jstring)env->CallObjectMethod(j, mid);
	const char *stringVal = env->GetStringUTFChars(jstringVal, 0);
	return new UTHString(stringVal);
}

UTHValue* j_to_uth_number(JNIEnv *env, jclass jCls, jobject j)
{
	jmethodID isIntMid = env->GetMethodID(jCls, "isInt", "()Z");
	jboolean isInt = env->CallBooleanMethod(j, isIntMid);
	if(isInt)
	{
		jmethodID intValueMid = env->GetMethodID(jCls, "intValue", "()I");
		jint intVal = env->CallIntMethod(j, intValueMid);
		return new UTHNumber((int)intVal);
	}
	else
	{
		jmethodID doubleValueMid = env->GetMethodID(jCls, "doubleValue", "()D");
		jdouble doubleVal = env->CallDoubleMethod(j, doubleValueMid);
		return new UTHNumber(doubleVal);
	}
}

UTHValue* j_to_uth_object(JNIEnv *env, jclass jCls, jobject j)
{
	UTHObject *obj = new UTHObject();
	
	jmethodID getKeysMid = env->GetMethodID(jCls, "getKeys", "()[Ljava/lang/String;");
	jmethodID getMid = env->GetMethodID(jCls, "get", "(Ljava/lang/String;)Lorg/phnq/babel/types/JSValue;");
	
	jobjectArray keys = (jobjectArray)env->CallObjectMethod(j, getKeysMid);
	jsize i = env->GetArrayLength(keys);
	while(i--)
	{
		jstring jkey = (jstring)env->GetObjectArrayElement(keys, i);
		const char *key = env->GetStringUTFChars(jkey, 0);
		jobject jval = env->CallObjectMethod(j, getMid, jkey);
		UTHValue *val = j_to_uth_val(env, jval);
		obj->set((char*)key, val);
	}

	return obj;
}

UTHValue* j_to_uth_array(JNIEnv *env, jclass jCls, jobject j)
{
	UTHArray *arr = new UTHArray();
	
	jmethodID lengthMid = env->GetMethodID(jCls, "length", "()I");
	jmethodID getMid = env->GetMethodID(jCls, "get", "(I)Lorg/phnq/babel/types/JSValue;");

	jint length = env->CallIntMethod(j, lengthMid);

	for(jint i=0; i<length; i++)
	{
		jobject jval = env->CallObjectMethod(j, getMid, i);
		arr->add(j_to_uth_val(env, jval));
	}
	
	return arr;
}
